﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Diagnostics.Contracts;
using ShredTheLog.Common;
using System.Xml.Linq;
using System.ComponentModel.Composition;
using ShredTheLog.Api.DataContracts;
using ShredTheLog.Api;
using System.Diagnostics;

namespace ShredTheLog.Translation
{
    [Export(typeof(ITranslator))]
    [OrderMetadata(Order = 100)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class MicrosoftTranslator : ITranslator
    {
        public string ID { get { return "MS"; } }
        public string Name { get { return "Microsoft Translator"; } }

        private const string AppId = "FF7C6FEBD7FC3F46C1505C9534C5767885EF1715";
        private const string BaseUriString = "http://api.microsofttranslator.com/V1/Http.svc/";
        private static readonly string TranslateUriString = String.Format(CultureInfo.InvariantCulture, "{0}Translate?appId={1}&", BaseUriString, AppId);
        private static readonly Uri DetectUri = new Uri(String.Format(CultureInfo.InvariantCulture, "{0}Detect?appId={1}&", BaseUriString, AppId));
        private static readonly Uri GetLanguagesUri = new Uri(String.Format(CultureInfo.InvariantCulture, "{0}GetLanguages?appId={1}&", BaseUriString, AppId));
        private static readonly string GetLanguageNamesUriString = String.Format(CultureInfo.InvariantCulture, "{0}GetLanguageNames?appId={1}&", BaseUriString, AppId);

        private static readonly Dictionary<Language, string> LanguageCodes = new Dictionary<Language, string>()
        {
            { Language.Arabic, "ar" },
            { Language.Bulgarian, "bg" },
            { Language.ChineseMandarin, "zh-CHS" },
            { Language.ChineseOther, "zh-CHT" },
            { Language.Czech, "cs" },
            { Language.Danish, "da" },
            { Language.Dutch, "nl" },
            { Language.English, "en" },
            { Language.Finnish, "fi" },
            { Language.French, "fr" },
            { Language.German, "de" },
            { Language.Greek, "el" },
            { Language.Hebrew, "he" },
            { Language.Italian, "it" },
            { Language.Japanese, "ja" },
            { Language.Korean, "ko" },
            { Language.Polish, "pl" },
            { Language.Portuguese, "pt" },
            { Language.Russian, "ru" },
            { Language.Spanish, "es" },
            { Language.Swedish, "sv" },
            { Language.Thai, "th" },

            // Эти языки иногда определяются методом Detect, но перевод с этих языков на другие не работает.
            //{ Language.Azerbaijani, "az" },
            //{ Language.Estonian, "et" }
        };

        private Language GetLanguage(string languageCode)
        {
            Contract.Requires(!String.IsNullOrWhiteSpace(languageCode));

            var result = LanguageCodes.SingleOrDefault(i => i.Value == languageCode);
            if (result.Value != null)
            {
                return result.Key;
            }
            else
            {
                Trace.WriteLine(String.Format("{0}: Language code {1} was not recognized.", this.GetType().Name, languageCode));
                return Language.English;
            }
        }

        public IObservable<TranslateResponse> Translate(TranslateRequest translateRequest, bool ignoreErrors)
        {
            if (String.IsNullOrWhiteSpace(translateRequest.Text))
            {
                var result1 = Observable.Return(new TranslateResponse(
                    translateRequest.Text, 
                    translateRequest.SourceLanguage.HasValue ? translateRequest.SourceLanguage.Value : Language.English, 
                    translateRequest.Context));
                Contract.Assume(result1 != null);
                return result1;
            }

            if (translateRequest.SourceLanguage.HasValue)
            {
                var result2 =
                    from response in Translate(translateRequest.Text, translateRequest.SourceLanguage.Value, translateRequest.TargetLanguage, ignoreErrors)
                    select new TranslateResponse(response, translateRequest.SourceLanguage.Value, translateRequest.Context);
                Contract.Assume(result2 != null);
                return result2;
            }
            else
            {
                var result3 =
                    from detectResponse in Detect(translateRequest.Text)
                    from response in Translate(translateRequest.Text, detectResponse.DetectedLanguage, translateRequest.TargetLanguage, ignoreErrors)
                    select new TranslateResponse(response, detectResponse.DetectedLanguage, translateRequest.Context);
                Contract.Assume(result3 != null);
                return result3;
            }
        }

        public IObservable<DetectResponse> Detect(string text)
        {
            if (String.IsNullOrWhiteSpace(text))
            {
                var result1 = Observable.Return(new DetectResponse(Language.English));
                Contract.Assume(result1 != null);
                return result1;
            }

            var result2 =
                from responseString in WebHelper.PostHttpRequest(DetectUri, text, Encoding.UTF8, Encoding.UTF8, "text/plain")
                let detectedLanguage = GetLanguage(responseString)
                select new DetectResponse(detectedLanguage);
            Contract.Assume(result2 != null);
            return result2;
        }

        public IEnumerable<Language> SupportedLanguages { get { return LanguageCodes.Keys; } }

        public override string ToString()
        {
            return Name;
        }

        public IObservable<string> Translate(string text, Language sourceLanguage, Language targetLanguage, bool ignoreErrors)
        {
            if (String.IsNullOrWhiteSpace(text) || sourceLanguage == targetLanguage)
                return Observable.Return(text);

            string uriParams = String.Format(CultureInfo.InvariantCulture, "from={0}&to={1}",
                LanguageCodes[sourceLanguage],
                LanguageCodes[targetLanguage]);

            return
                WebHelper.PostHttpRequest(new Uri(TranslateUriString + uriParams), text, Encoding.UTF8, Encoding.UTF8, "text/plain");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>A list of language codes that are supported by the Translation Service.</returns>
        public IObservable<IEnumerable<Language>> GetLanguages()
        {
            var result =
                from response in WebHelper.GetHttpRequest(GetLanguagesUri, Encoding.UTF8)
                select (from langCode in response.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) select GetLanguage(langCode));
            return result;
        }

        /// <summary>
        /// Obtains a list of languages available for translation, with the language names are localized into a desired language.
        /// </summary>
        /// <param name="locale">Language code to localize the language names.</param>
        /// <returns>A string array containing languages names supported by the Translator Service, localized into the requested language.</returns>
        public IObservable<IEnumerable<string>> GetLanguageNames(Language? locale)
        {
            var result =
                from response in WebHelper.GetHttpRequest(new Uri(GetLanguageNamesUriString + (locale.HasValue ? "locale=" + LanguageCodes[locale.Value] : String.Empty)), Encoding.UTF8)
                select response.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            return result;
        }
    }
}
